import re
import pandas as pd
from typing import List, Tuple
import networkx as nx

def parse_action_switch(output_text):
    actions = []
    find_action = False
    # 动作列表文本示例"action":["(748,749,1)","(545,575,0)"]
    # 找到开关动作列表
    action_list_match = re.search(r'"action"\s*:\s*\[([^\]]*)\]', output_text)

    # 遍历所有找到的动作
    if action_list_match:
        find_action = True
        action_content = action_list_match.group(1)
        tuples = re.finditer(r'\((\d+),(\d+),(\d+)\)', action_content)

        # 将匹配到的元组转换为Python元组存储到actions中
        for match in tuples:
            from_bus = int(match.group(1))
            to_bus = int(match.group(2))
            status = int(match.group(3))
            actions.append((from_bus, to_bus, status))

    return actions, len(actions), find_action


def compute_dis_invalid_loss(
    branches: pd.DataFrame, actions: List[Tuple[int, int , int]]
) -> int:
    """
    计算无效开关动作数量作为invalid loss

    Args:
        branches: 输入表格
        actions: 模型输出解析出来的开关动作列表[(from,to,status),...]

    Returns:
        invalid_loss: 无效动作数量

    功能说明：
        计算actions中开关动作的无效数量。无效动作定义为：
        1. 线路的开关状态与输入表格中一致
        2. 线路的开关状态与输入表格中不一致，但输入表格中该线路未配备开关
    """
    invalid_count = 0

    for from_bus, to_bus, new_status in actions:
        # Find matching branches (in either direction)
        matched_branches = branches[
            ((branches["F-Node-map"] == from_bus) & (branches["T-Node-map"] == to_bus))
            | ((branches["F-Node-map"] == to_bus) & (branches["T-Node-map"] == from_bus))
        ]

        if len(matched_branches) == 0:
            # No matching branch found - invalid action
            invalid_count += 1
            continue

        branch = matched_branches.iloc[0]  # Get first matching branch

        # Check if the branch has a switch
        has_switch = branch['Switch'] == 'Y'

        if not has_switch:
            # Branch doesn't have a switch - invalid action
            invalid_count += 1
            continue

        current_status = branch['SwitchState']

        # Check if the action would change the status (1: closed, 0/-1: open)
        would_change = (
            (new_status == 1 and current_status != 1) or 
            (new_status == 0 and current_status == 1)
        )

        if not would_change:
            # Action doesn't change current status - invalid
            invalid_count += 1

    return invalid_count


def compute_dis_cycles_and_unsupply(
    branches: pd.DataFrame, actions: List[Tuple[int, int, int]],loads: pd.DataFrame,source_bus: List[int]
) -> Tuple[int,float]:
    """
    计算配电网中的环网数量作为cycles loss
    计算配电网中的未恢复负荷总量作为unsupply loss

    Args:
        branches: 输入支路表格
        loades: 输入负荷表格
        actions: 模型输出解析出来的开关动作列表[(from,to,status),...]
        source_bus: 充当电源节点的列表

    Returns:
        int: 环网数量的损失值
        double: 停电负荷总量

    功能说明：
        根据开关动作列表更新branches支路表格中的支路状态，删除断开的支路，使用闭合支路形成topo_edges。
        然后使用networkx计算环网数量和未恢复负荷总量。
    """
    # Create a working copy of the branches data
    branches_df = branches.copy()

    # Create a dictionary to store switch status updates
    switch_updates = {
        (from_bus, to_bus): status for from_bus, to_bus, status in actions
    }

    # Apply switch actions to update the network topology
    # 时间来不及用差集了，先这样吧
    topo_edges = []
    for _, branch in branches_df.iterrows():
        from_bus = branch["F-Node-map"]
        to_bus = branch["T-Node-map"]

        # Check if this branch has a switch action
        action_status = None
        if (from_bus, to_bus) in switch_updates:
            action_status = switch_updates[(from_bus, to_bus)]
        elif (to_bus, from_bus) in switch_updates:
            action_status = switch_updates[(to_bus, from_bus)]

        if action_status is not None:
            # If there's an action, use the new status
            is_closed = action_status == 1
        else:
            # Otherwise use the original status
            is_closed = (branch["SwitchState"] == 1 or branch["SwitchState"] == -1)

        # Only include the branch if it's closed and has a switch
        if branch["Switch"] == "Y" and is_closed:
            topo_edges.append((from_bus, to_bus))
        elif branch["Switch"] != "Y":
            # Non-switch branches are always included
            topo_edges.append((from_bus, to_bus))
    num_cycles = 0
    unsupplied_load = 0.0
    # Create the network graph
    G = nx.Graph()
    G.add_edges_from(topo_edges)

    # Calculate the number of cycles
    try:
        cycles = list(nx.cycle_basis(G))
        num_cycles= len(cycles)
    except nx.NetworkXNoCycle:
        num_cycles = 0
    
    # Find all nodes connected to source buses
    connected_nodes = set()
    for source in source_bus:
        if source in G:
            connected_nodes.update(nx.node_connected_component(G, source))

    # Get list of all load nodes in the network
    load_nodes = set(loads["Node-map"].unique())

    # Calculate unsupplied load (nodes not connected to any source)
    unsupplied_nodes = load_nodes - connected_nodes
    unsupplied_load = loads[loads["Node-map"].isin(unsupplied_nodes)]["P"].sum()

    return num_cycles,unsupplied_load

